最小権限のIAM Policy作成にCloudFormationのコマンドが役立つ
最小権限のIAM Policyを作成するのって地味に面倒ですよね。以前私は、Route53ホストゾーンにDNSレコード作成するのに必要な最小権限のPolicyを作るため、権限ゼロの状態から始めて、権限不足エラーが出るたびに権限を足していくという力技でPolicyを作ったことがあります。
もうちょっとスマートなやり方が、CloudFormation(CFn)のコマンドを使うとできる場合があることを学んだのでレポートします。
aws cloudformation describe-type
そのコマンドが、 aws cloudformation describe-type
です。--type
オプションでRESOURCE
を指定して、 --type-name
でCFnのリソースタイプネーム(AWS::EC2::VPC
みたいなやつのことです)を指定すると、そのリソースタイプの詳細情報を返してくれます。
{ "Arn": "arn:aws:cloudformation:ap-northeast-1::type/resource/AWS-EC2-VPC", "Type": "RESOURCE", "TypeName": "AWS::EC2::VPC", "IsDefaultVersion": true, "Description": "Resource Type definition for AWS::EC2::VPC", "Schema": "{\n \"typeName\": \"AWS::EC2::VPC\",\n \"description\": \"Resource Type definition for AWS::EC2::VPC\",\n \"additionalProperties\": false,\n \"properties\": {\n \"Id\": {\n \"type\": \"string\",\n \"description\": \"The Id for the model.\"\n },\n \"CidrBlock\": {\n \"type\": \"string\",\n \"description\": \"The primary IPv4 CIDR block for the VPC.\"\n },\n \"CidrBlockAssociations\": {\n \"type\": \"array\",\n \"description\": \"A list of IPv4 CIDR block association IDs for the VPC.\",\n \"uniqueItems\": false,\n \"insertionOrder\": false,\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"DefaultNetworkAcl\": {\n \"type\": \"string\",\n \"insertionOrder\": false,\n \"description\": \"The default network ACL ID that is associated with the VPC.\"\n },\n \"DefaultSecurityGroup\": {\n \"type\": \"string\",\n \"insertionOrder\": false,\n \"description\": \"The default security group ID that is associated with the VPC.\"\n },\n \"Ipv6CidrBlocks\": {\n \"type\": \"array\",\n \"description\": \"A list of IPv6 CIDR blocks that are associated with the VPC.\",\n \"uniqueItems\": false,\n \"insertionOrder\": false,\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"EnableDnsHostnames\": {\n \"type\": \"boolean\",\n \"description\": \"Indicates whether the instances launched in the VPC get DNS hostnames. If enabled, instances in the VPC get DNS hostnames; otherwise, they do not. Disabled by default for nondefault VPCs.\"\n },\n \"EnableDnsSupport\": {\n \"type\": \"boolean\",\n \"description\": \"Indicates whether the DNS resolution is supported for the VPC. If enabled, queries to the Amazon provided DNS server at the 169.254.169.253 IP address, or the reserved IP address at the base of the VPC network range \\\"plus two\\\" succeed. If disabled, the Amazon provided DNS service in the VPC that resolves public DNS hostnames to IP addresses is not enabled. Enabled by default.\"\n },\n \"InstanceTenancy\": {\n \"type\": \"string\",\n \"description\": \"The allowed tenancy of instances launched into the VPC.\\n\\n\\\"default\\\": An instance launched into the VPC runs on shared hardware by default, unless you explicitly specify a different tenancy during instance launch.\\n\\n\\\"dedicated\\\": An instance launched into the VPC is a Dedicated Instance by default, unless you explicitly specify a tenancy of host during instance launch. You cannot specify a tenancy of default during instance launch.\\n\\nUpdating InstanceTenancy requires no replacement only if you are updating its value from \\\"dedicated\\\" to \\\"default\\\". Updating InstanceTenancy from \\\"default\\\" to \\\"dedicated\\\" requires replacement.\"\n },\n \"Tags\": {\n \"type\": \"array\",\n \"description\": \"The tags for the VPC.\",\n \"uniqueItems\": false,\n \"insertionOrder\": false,\n \"items\": {\n \"$ref\": \"#/definitions/Tag\"\n }\n }\n },\n \"definitions\": {\n \"Tag\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"Key\": {\n \"type\": \"string\"\n },\n \"Value\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"Value\",\n \"Key\"\n ]\n }\n },\n \"taggable\": true,\n \"required\": [\n \"CidrBlock\"\n ],\n \"createOnlyProperties\": [\n \"/properties/CidrBlock\"\n ],\n \"conditionalCreateOnlyProperties\": [\n \"/properties/InstanceTenancy\"\n ],\n \"readOnlyProperties\": [\n \"/properties/Id\",\n \"/properties/DefaultSecurityGroup\",\n \"/properties/CidrBlockAssociations\",\n \"/properties/DefaultNetworkAcl\",\n \"/properties/Ipv6CidrBlocks\"\n ],\n \"primaryIdentifier\": [\n \"/properties/Id\"\n ],\n \"handlers\": {\n \"create\": {\n \"permissions\": [\n \"ec2:CreateVpc\",\n \"ec2:CreateTags\",\n \"ec2:ModifyVpcAttribute\",\n \"ec2:DescribeVpcs\",\n \"ec2:DescribeSecurityGroups\",\n \"ec2:DescribeNetworkAcls\",\n \"ec2:DescribeVpcAttribute\"\n ]\n },\n \"read\": {\n \"permissions\": [\n \"ec2:DescribeVpcs\",\n \"ec2:DescribeSecurityGroups\",\n \"ec2:DescribeNetworkAcls\",\n \"ec2:DescribeVpcAttribute\"\n ]\n },\n \"update\": {\n \"permissions\": [\n \"ec2:CreateTags\",\n \"ec2:ModifyVpcAttribute\",\n \"ec2:DescribeVpcs\",\n \"ec2:DeleteTags\",\n \"ec2:ModifyVpcTenancy\",\n \"ec2:DescribeSecurityGroups\",\n \"ec2:DescribeNetworkAcls\",\n \"ec2:DescribeVpcAttribute\"\n ]\n },\n \"delete\": {\n \"permissions\": [\n \"ec2:DeleteVpc\",\n \"ec2:DescribeVpcs\",\n \"ec2:DeleteTags\"\n ]\n },\n \"list\": {\n \"permissions\": [\n \"ec2:DescribeVpcs\"\n ]\n }\n }\n}\n", "ProvisioningType": "FULLY_MUTABLE", "DeprecatedStatus": "LIVE", "Visibility": "PUBLIC", "TimeCreated": "2021-08-20T20:25:24.577000+00:00" }
このコマンドの出力値Schema
、そのままだとパースされていないので読みにくのですが、jqを使ってパースしてみます。
{ "typeName": "AWS::EC2::VPC", "description": "Resource Type definition for AWS::EC2::VPC", "additionalProperties": false, "properties": { "Id": { "type": "string", "description": "The Id for the model." }, "CidrBlock": { "type": "string", "description": "The primary IPv4 CIDR block for the VPC." }, "CidrBlockAssociations": { "type": "array", "description": "A list of IPv4 CIDR block association IDs for the VPC.", "uniqueItems": false, "insertionOrder": false, "items": { "type": "string" } }, "DefaultNetworkAcl": { "type": "string", "insertionOrder": false, "description": "The default network ACL ID that is associated with the VPC." }, "DefaultSecurityGroup": { "type": "string", "insertionOrder": false, "description": "The default security group ID that is associated with the VPC." }, "Ipv6CidrBlocks": { "type": "array", "description": "A list of IPv6 CIDR blocks that are associated with the VPC.", "uniqueItems": false, "insertionOrder": false, "items": { "type": "string" } }, "EnableDnsHostnames": { "type": "boolean", "description": "Indicates whether the instances launched in the VPC get DNS hostnames. If enabled, instances in the VPC get DNS hostnames; otherwise, they do not. Disabled by default for nondefault VPCs." }, "EnableDnsSupport": { "type": "boolean", "description": "Indicates whether the DNS resolution is supported for the VPC. If enabled, queries to the Amazon provided DNS server at the 169.254.169.253 IP address, or the reserved IP address at the base of the VPC network range \"plus two\" succeed. If disabled, the Amazon provided DNS service in the VPC that resolves public DNS hostnames to IP addresses is not enabled. Enabled by default." }, "InstanceTenancy": { "type": "string", "description": "The allowed tenancy of instances launched into the VPC.\n\n\"default\": An instance launched into the VPC runs on shared hardware by default, unless you explicitly specify a different tenancy during instance launch.\n\n\"dedicated\": An instance launched into the VPC is a Dedicated Instance by default, unless you explicitly specify a tenancy of host during instance launch. You cannot specify a tenancy of default during instance launch.\n\nUpdating InstanceTenancy requires no replacement only if you are updating its value from \"dedicated\" to \"default\". Updating InstanceTenancy from \"default\" to \"dedicated\" requires replacement." }, "Tags": { "type": "array", "description": "The tags for the VPC.", "uniqueItems": false, "insertionOrder": false, "items": { "$ref": "#/definitions/Tag" } } }, "definitions": { "Tag": { "type": "object", "additionalProperties": false, "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } }, "required": [ "Value", "Key" ] } }, "taggable": true, "required": [ "CidrBlock" ], "createOnlyProperties": [ "/properties/CidrBlock" ], "conditionalCreateOnlyProperties": [ "/properties/InstanceTenancy" ], "readOnlyProperties": [ "/properties/Id", "/properties/DefaultSecurityGroup", "/properties/CidrBlockAssociations", "/properties/DefaultNetworkAcl", "/properties/Ipv6CidrBlocks" ], "primaryIdentifier": [ "/properties/Id" ], "handlers": { "create": { "permissions": [ "ec2:CreateVpc", "ec2:CreateTags", "ec2:ModifyVpcAttribute", "ec2:DescribeVpcs", "ec2:DescribeSecurityGroups", "ec2:DescribeNetworkAcls", "ec2:DescribeVpcAttribute" ] }, "read": { "permissions": [ "ec2:DescribeVpcs", "ec2:DescribeSecurityGroups", "ec2:DescribeNetworkAcls", "ec2:DescribeVpcAttribute" ] }, "update": { "permissions": [ "ec2:CreateTags", "ec2:ModifyVpcAttribute", "ec2:DescribeVpcs", "ec2:DeleteTags", "ec2:ModifyVpcTenancy", "ec2:DescribeSecurityGroups", "ec2:DescribeNetworkAcls", "ec2:DescribeVpcAttribute" ] }, "delete": { "permissions": [ "ec2:DeleteVpc", "ec2:DescribeVpcs", "ec2:DeleteTags" ] }, "list": { "permissions": [ "ec2:DescribeVpcs" ] } } }
一番下のhandlers
プロパティに注目です。そこだけ抜き出してみます。
{ "create": { "permissions": [ "ec2:CreateVpc", "ec2:CreateTags", "ec2:ModifyVpcAttribute", "ec2:DescribeVpcs", "ec2:DescribeSecurityGroups", "ec2:DescribeNetworkAcls", "ec2:DescribeVpcAttribute" ] }, "read": { "permissions": [ "ec2:DescribeVpcs", "ec2:DescribeSecurityGroups", "ec2:DescribeNetworkAcls", "ec2:DescribeVpcAttribute" ] }, "update": { "permissions": [ "ec2:CreateTags", "ec2:ModifyVpcAttribute", "ec2:DescribeVpcs", "ec2:DeleteTags", "ec2:ModifyVpcTenancy", "ec2:DescribeSecurityGroups", "ec2:DescribeNetworkAcls", "ec2:DescribeVpcAttribute" ] }, "delete": { "permissions": [ "ec2:DeleteVpc", "ec2:DescribeVpcs", "ec2:DeleteTags" ] }, "list": { "permissions": [ "ec2:DescribeVpcs" ] } }
対象リソース(この例だとVPC)に対する各種操作(create/read/update/delete/list)に必要な権限の一覧が出力されています。 CFnを実行するIAMエンティティの最小ポリシーを作りたいのであれば、テンプレート内のリソースタイプを一つずつ--type-name
に指定してこのコマンドを実行すれば、最小権限がすぐわかりますね。それ以外の場合でも、やりたいこととCFnリソースタイプの関係さえ掴めればこのコマンドを使って最小権限を調べることができます。
全リソースに対応しているわけではない
残念ながらこのhandler
プロパティは全てのリソースタイプで出力してくれるわけではありません。例えば最初に挙げたRoute53ホストゾーンのDNSレコード、これは対応していませんでした。
{ "typeName": "AWS::Route53::RecordSet", "description": "Resource Type definition for AWS::Route53::RecordSet", "additionalProperties": false, "properties": { "Id": { "type": "string" }, "AliasTarget": { "$ref": "#/definitions/AliasTarget" }, "Comment": { "type": "string" }, "Failover": { "type": "string" }, "GeoLocation": { "$ref": "#/definitions/GeoLocation" }, "HealthCheckId": { "type": "string" }, "HostedZoneId": { "type": "string" }, "HostedZoneName": { "type": "string" }, "MultiValueAnswer": { "type": "boolean" }, "Name": { "type": "string" }, "Region": { "type": "string" }, "ResourceRecords": { "type": "array", "uniqueItems": false, "items": { "type": "string" } }, "SetIdentifier": { "type": "string" }, "TTL": { "type": "string" }, "Type": { "type": "string" }, "Weight": { "type": "integer" } }, "definitions": { "AliasTarget": { "type": "object", "additionalProperties": false, "properties": { "DNSName": { "type": "string" }, "EvaluateTargetHealth": { "type": "boolean" }, "HostedZoneId": { "type": "string" } }, "required": [ "DNSName", "HostedZoneId" ] }, "GeoLocation": { "type": "object", "additionalProperties": false, "properties": { "ContinentCode": { "type": "string" }, "CountryCode": { "type": "string" }, "SubdivisionCode": { "type": "string" } } } }, "required": [ "Type", "Name" ], "createOnlyProperties": [ "/properties/HostedZoneName", "/properties/Name", "/properties/HostedZoneId" ], "primaryIdentifier": [ "/properties/Id" ], "readOnlyProperties": [ "/properties/Id" ] }
どうやら aws cloudformation describe-type
の返り値 ProvisioningType
の値がNON_PROVISIONABLE
の物はhandler
プロパティを出力しないようです。(ProvisioningType
の値はNON_PROVISIONABLE
、IMMUTABLE
、FULLY_MUTABLE
の3パターンあります。)
最小権限のIAM Policyを作成する別の方法
IAM Access Analyzerを使えば、実際の操作履歴からPolicyを作ることができます。一度広めの権限を与えてから絞っていくようなアプローチですね。